/** @file
  Implements functions to pad firmware file.

  Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "FwVolDriver.h"

/**
  Calculate the checksum for a PAD file.

  @param PadFileHeader   The Pad File to be caculeted the checksum.

**/
VOID
SetPadFileChecksum (
  IN EFI_FFS_FILE_HEADER *PadFileHeader
  )
{
  if ((PadFileHeader->Attributes & FFS_ATTRIB_CHECKSUM) != 0) {

    if (IS_FFS_FILE2 (PadFileHeader)) {
      //
      // Calculate checksum of Pad File Data
      //
      PadFileHeader->IntegrityCheck.Checksum.File =
        CalculateCheckSum8 ((UINT8 *) PadFileHeader + sizeof (EFI_FFS_FILE_HEADER2), FFS_FILE2_SIZE (PadFileHeader) - sizeof (EFI_FFS_FILE_HEADER2));

      } else {
      //
      // Calculate checksum of Pad File Data
      //
      PadFileHeader->IntegrityCheck.Checksum.File =
        CalculateCheckSum8 ((UINT8 *) PadFileHeader + sizeof (EFI_FFS_FILE_HEADER), FFS_FILE_SIZE (PadFileHeader) - sizeof (EFI_FFS_FILE_HEADER));
    }

  } else {

    PadFileHeader->IntegrityCheck.Checksum.File = FFS_FIXED_CHECKSUM;

  }

  return ;
}

/**
  Create a PAD File in the Free Space.

  @param FvDevice        Firmware Volume Device.
  @param FreeSpaceEntry  Indicating in which Free Space(Cache) the Pad file will be inserted.
  @param Size            Pad file Size, not include the header.
  @param PadFileEntry    The Ffs File Entry that points to this Pad File.

  @retval EFI_SUCCESS            Successfully create a PAD file.
  @retval EFI_OUT_OF_RESOURCES   No enough free space to create a PAD file.
  @retval EFI_INVALID_PARAMETER  Size is not 8 byte alignment.
  @retval EFI_DEVICE_ERROR       Free space is not erased.
**/
EFI_STATUS
FvCreatePadFileInFreeSpace (
  IN  FV_DEVICE           *FvDevice,
  IN  FREE_SPACE_ENTRY    *FreeSpaceEntry,
  IN  UINTN               Size,
  OUT FFS_FILE_LIST_ENTRY **PadFileEntry
  )
{
  EFI_STATUS                          Status;
  EFI_FFS_FILE_HEADER                 *PadFileHeader;
  UINTN                               Offset;
  UINTN                               NumBytesWritten;
  UINTN                               StateOffset;
  UINT8                               *StartPos;
  FFS_FILE_LIST_ENTRY                 *FfsFileEntry;
  UINTN                               HeaderSize;
  UINTN                               FileSize;

  HeaderSize = sizeof (EFI_FFS_FILE_HEADER);
  FileSize = Size + HeaderSize;
  if (FileSize > 0x00FFFFFF) {
    HeaderSize = sizeof (EFI_FFS_FILE_HEADER2);
    FileSize = Size + HeaderSize;
  }

  if (FreeSpaceEntry->Length < FileSize) {
    return EFI_OUT_OF_RESOURCES;
  }

  if ((Size & 0x07) != 0) {
    return EFI_INVALID_PARAMETER;
  }

  StartPos = FreeSpaceEntry->StartingAddress;

  //
  // First double check the space
  //
  if (!IsBufferErased (
        FvDevice->ErasePolarity,
        StartPos,
        FileSize
        )) {
    return EFI_DEVICE_ERROR;
  }

  PadFileHeader = (EFI_FFS_FILE_HEADER *) StartPos;

  //
  // Create File Step 1
  //
  SetFileState (EFI_FILE_HEADER_CONSTRUCTION, PadFileHeader);

  Offset          = (UINTN) (StartPos - FvDevice->CachedFv);
  StateOffset     = Offset + (UINT8 *) &PadFileHeader->State - (UINT8 *) PadFileHeader;

  NumBytesWritten = sizeof (EFI_FFS_FILE_STATE);
  Status = FvcWrite (
            FvDevice,
            StateOffset,
            &NumBytesWritten,
            &PadFileHeader->State
            );
  if (EFI_ERROR (Status)) {
    SetFileState (EFI_FILE_HEADER_CONSTRUCTION, PadFileHeader);
    return Status;
  }
  //
  // Update Free Space Entry, since header is allocated
  //
  FreeSpaceEntry->Length -= HeaderSize;
  FreeSpaceEntry->StartingAddress += HeaderSize;

  //
  // Fill File Name Guid, here we assign a NULL-GUID to Pad files
  //
  ZeroMem (&PadFileHeader->Name, sizeof (EFI_GUID));

  //
  // Fill File Type, checksum(0), Attributes(0), Size
  //
  PadFileHeader->Type       = EFI_FV_FILETYPE_FFS_PAD;
  PadFileHeader->Attributes = 0;
  if ((FileSize) > 0x00FFFFFF) {
    ((EFI_FFS_FILE_HEADER2 *) PadFileHeader)->ExtendedSize = (UINT32) FileSize;
    *(UINT32 *) PadFileHeader->Size &= 0xFF000000;
    PadFileHeader->Attributes |= FFS_ATTRIB_LARGE_FILE;
  } else {
    *(UINT32 *) PadFileHeader->Size &= 0xFF000000;
    *(UINT32 *) PadFileHeader->Size |= FileSize;
  }

  SetHeaderChecksum (PadFileHeader);
  SetPadFileChecksum (PadFileHeader);

  Offset          = (UINTN) (StartPos - FvDevice->CachedFv);

  NumBytesWritten = HeaderSize;
  Status = FvcWrite (
            FvDevice,
            Offset,
            &NumBytesWritten,
            (UINT8 *) PadFileHeader
            );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Step 2, then Mark header valid, since no data write,
  // mark the data valid at the same time.
  //
  SetFileState (EFI_FILE_HEADER_VALID, PadFileHeader);
  SetFileState (EFI_FILE_DATA_VALID, PadFileHeader);

  Offset          = (UINTN) (StartPos - FvDevice->CachedFv);
  StateOffset     = Offset + (UINT8 *) &PadFileHeader->State - (UINT8 *) PadFileHeader;

  NumBytesWritten = sizeof (EFI_FFS_FILE_STATE);
  Status = FvcWrite (
            FvDevice,
            StateOffset,
            &NumBytesWritten,
            &PadFileHeader->State
            );
  if (EFI_ERROR (Status)) {
    SetFileState (EFI_FILE_HEADER_VALID, PadFileHeader);
    SetFileState (EFI_FILE_DATA_VALID, PadFileHeader);
    return Status;
  }
  //
  // Update Free Space Entry, since header is allocated
  //
  FreeSpaceEntry->Length -= Size;
  FreeSpaceEntry->StartingAddress += Size;

  //
  // If successfully, insert an FfsFileEntry at the end of ffs file list
  //
  FfsFileEntry = AllocateZeroPool (sizeof (FFS_FILE_LIST_ENTRY));
  ASSERT (FfsFileEntry != NULL);

  FfsFileEntry->FfsHeader = (UINT8 *) (UINTN) StartPos;
  InsertTailList (&FvDevice->FfsFileListHeader, &FfsFileEntry->Link);

  *PadFileEntry             = FfsFileEntry;
  FvDevice->CurrentFfsFile  = FfsFileEntry;

  return EFI_SUCCESS;
}

/**
  Fill pad file header within firmware cache.

  @param PadFileHeader    The start of the Pad File Buffer.
  @param PadFileLength    The length of the pad file including the header.

**/
VOID
FvFillPadFile (
  IN EFI_FFS_FILE_HEADER  *PadFileHeader,
  IN UINTN                PadFileLength
  )
{
  //
  // Fill File Name Guid, here we assign a NULL-GUID to Pad files
  //
  ZeroMem (&PadFileHeader->Name, sizeof (EFI_GUID));

  //
  // Fill File Type, checksum(0), Attributes(0), Size
  //
  PadFileHeader->Type       = EFI_FV_FILETYPE_FFS_PAD;
  PadFileHeader->Attributes = 0;
  if (PadFileLength > 0x00FFFFFF) {
    ((EFI_FFS_FILE_HEADER2 *) PadFileHeader)->ExtendedSize = (UINT32) PadFileLength;
    *(UINT32 *) PadFileHeader->Size &= 0xFF000000;
    PadFileHeader->Attributes |= FFS_ATTRIB_LARGE_FILE;
  } else {
    *(UINT32 *) PadFileHeader->Size &= 0xFF000000;
    *(UINT32 *) PadFileHeader->Size |= PadFileLength;
  }

  SetHeaderChecksum (PadFileHeader);
  SetPadFileChecksum (PadFileHeader);

  //
  // Set File State to 0x00000111
  //
  SetFileState (EFI_FILE_HEADER_CONSTRUCTION, PadFileHeader);
  SetFileState (EFI_FILE_HEADER_VALID, PadFileHeader);
  SetFileState (EFI_FILE_DATA_VALID, PadFileHeader);

  return ;
}

/**
  Create entire FFS file.

  @param FileHeader      Starting Address of a Buffer that hold the FFS File image.
  @param FfsFileBuffer   The source buffer that contains the File Data.
  @param BufferSize      The length of FfsFileBuffer.
  @param ActualFileSize  Size of FFS file.
  @param FileName        The Guid of Ffs File.
  @param FileType        The type of the written Ffs File.
  @param FileAttributes  The attributes of the written Ffs File.

  @retval EFI_INVALID_PARAMETER  File type is not valid.
  @retval EFI_SUCCESS            FFS file is successfully created.

**/
EFI_STATUS
FvFillFfsFile (
  OUT EFI_FFS_FILE_HEADER   *FileHeader,
  IN UINT8                  *FfsFileBuffer,
  IN UINTN                  BufferSize,
  IN UINTN                  ActualFileSize,
  IN EFI_GUID               *FileName,
  IN EFI_FV_FILETYPE        FileType,
  IN EFI_FV_FILE_ATTRIBUTES FileAttributes
  )
{
  EFI_FFS_FILE_ATTRIBUTES TmpFileAttribute;
  EFI_FFS_FILE_HEADER     *TmpFileHeader;

  //
  // File Type value 0x0E~0xE0 are reserved
  //
  if ((FileType > EFI_FV_FILETYPE_SMM_CORE) && (FileType < 0xE0)) {
    return EFI_INVALID_PARAMETER;
  }

  TmpFileHeader = (EFI_FFS_FILE_HEADER *) FfsFileBuffer;
  //
  // First fill all fields ready in FfsFileBuffer
  //
  CopyGuid (&TmpFileHeader->Name, FileName);
  TmpFileHeader->Type = FileType;

  //
  // Convert the FileAttributes to FFSFileAttributes
  //
  FvFileAttrib2FfsFileAttrib (FileAttributes, &TmpFileAttribute);

  TmpFileHeader->Attributes = TmpFileAttribute;

  if (ActualFileSize > 0x00FFFFFF) {
    ((EFI_FFS_FILE_HEADER2 *) FileHeader)->ExtendedSize = (UINT32) ActualFileSize;
    *(UINT32 *) FileHeader->Size &= 0xFF000000;
    FileHeader->Attributes |= FFS_ATTRIB_LARGE_FILE;
  } else {
    *(UINT32 *) FileHeader->Size &= 0xFF000000;
    *(UINT32 *) FileHeader->Size |= ActualFileSize;
  }

  SetHeaderChecksum (TmpFileHeader);
  SetFileChecksum (TmpFileHeader, ActualFileSize);

  SetFileState (EFI_FILE_HEADER_CONSTRUCTION, TmpFileHeader);
  SetFileState (EFI_FILE_HEADER_VALID, TmpFileHeader);
  SetFileState (EFI_FILE_DATA_VALID, TmpFileHeader);

  //
  // Copy data from FfsFileBuffer to FileHeader(cache)
  //
  CopyMem (FileHeader, FfsFileBuffer, BufferSize);

  return EFI_SUCCESS;
}

/**
  Fill some other extra space using 0xFF(Erase Value).

  @param  ErasePolarity  Fv erase value.
  @param  FileHeader     Point to the start of FFS File.
  @param  ExtraLength    The pading length.

**/
VOID
FvAdjustFfsFile (
  IN  UINT8                 ErasePolarity,
  IN  EFI_FFS_FILE_HEADER   *FileHeader,
  IN  UINTN                 ExtraLength
  )
{
  UINT8 *Ptr;
  UINT8 PadingByte;

  if (IS_FFS_FILE2 (FileHeader)) {
    Ptr         = (UINT8 *) FileHeader + FFS_FILE2_SIZE (FileHeader);
  } else {
    Ptr         = (UINT8 *) FileHeader + FFS_FILE_SIZE (FileHeader);
  }

  if (ErasePolarity == 0) {
    PadingByte = 0;
  } else {
    PadingByte = 0xFF;
  }
  //
  // Fill the non-used space with Padding Byte
  //
  SetMem (Ptr, ExtraLength, PadingByte);

  return ;
}

/**
  Free File List entry pointed by FileListHead.

  @param FileListHeader   FileListEntry Header.

**/
VOID
FreeFileList (
  IN  LIST_ENTRY  *FileListHead
  )
{
  FFS_FILE_LIST_ENTRY *FfsFileEntry;
  LIST_ENTRY      *NextEntry;

  FfsFileEntry = (FFS_FILE_LIST_ENTRY *) (FileListHead->ForwardLink);

  //
  // Loop the whole list entry to free resources
  //
  while (&FfsFileEntry->Link != FileListHead) {
    NextEntry = (&FfsFileEntry->Link)->ForwardLink;
    FreePool (FfsFileEntry);
    FfsFileEntry = (FFS_FILE_LIST_ENTRY *) NextEntry;
  }

  return ;
}

/**
  Create a new file within a PAD file area.

  @param FvDevice        Firmware Volume Device.
  @param FfsFileBuffer   A buffer that holds an FFS file,(it contains a File Header which is in init state).
  @param BufferSize      The size of FfsFileBuffer.
  @param ActualFileSize  The actual file length, it may not be multiples of 8.
  @param FileName        The FFS File Name.
  @param FileType        The FFS File Type.
  @param FileAttributes  The Attributes of the FFS File to be created.

  @retval EFI_SUCCESS           Successfully create a new file within the found PAD file area.
  @retval EFI_OUT_OF_RESOURCES  No suitable PAD file is found.
  @retval other errors          New file is created failed.

**/
EFI_STATUS
FvCreateNewFileInsidePadFile (
  IN  FV_DEVICE               *FvDevice,
  IN  UINT8                   *FfsFileBuffer,
  IN  UINTN                   BufferSize,
  IN  UINTN                   ActualFileSize,
  IN  EFI_GUID                *FileName,
  IN  EFI_FV_FILETYPE         FileType,
  IN  EFI_FV_FILE_ATTRIBUTES  FileAttributes
  )
{
  UINTN                               RequiredAlignment;
  FFS_FILE_LIST_ENTRY                 *PadFileEntry;
  EFI_STATUS                          Status;
  UINTN                               PadAreaLength;
  UINTN                               PadSize;
  EFI_FFS_FILE_HEADER                 *FileHeader;
  EFI_FFS_FILE_HEADER                 *OldPadFileHeader;
  EFI_FFS_FILE_HEADER                 *PadFileHeader;
  EFI_FFS_FILE_HEADER                 *TailPadFileHeader;
  UINTN                               StateOffset;
  UINTN                               Offset;
  UINTN                               NumBytesWritten;
  UINT8                               *StartPos;
  LIST_ENTRY                          NewFileList;
  FFS_FILE_LIST_ENTRY                 *NewFileListEntry;
  FFS_FILE_LIST_ENTRY                 *FfsEntry;
  FFS_FILE_LIST_ENTRY                 *NextFfsEntry;

  //
  // First get the required alignment from the File Attributes
  //
  RequiredAlignment = GetRequiredAlignment (FileAttributes);

  //
  // Find a suitable PAD File
  //
  Status = FvLocatePadFile (
            FvDevice,
            BufferSize,
            RequiredAlignment,
            &PadSize,
            &PadFileEntry
            );

  if (EFI_ERROR (Status)) {
    return EFI_OUT_OF_RESOURCES;
  }

  OldPadFileHeader = (EFI_FFS_FILE_HEADER *) PadFileEntry->FfsHeader;

  //
  // Step 1: Update Pad File Header
  //
  SetFileState (EFI_FILE_MARKED_FOR_UPDATE, OldPadFileHeader);

  StartPos = PadFileEntry->FfsHeader;

  Offset          = (UINTN) (StartPos - FvDevice->CachedFv);
  StateOffset     = Offset + (UINT8 *) &OldPadFileHeader->State - (UINT8 *) OldPadFileHeader;

  NumBytesWritten = sizeof (EFI_FFS_FILE_STATE);
  Status = FvcWrite (
            FvDevice,
            StateOffset,
            &NumBytesWritten,
            &OldPadFileHeader->State
            );
  if (EFI_ERROR (Status)) {
    SetFileState (EFI_FILE_HEADER_CONSTRUCTION, OldPadFileHeader);
    return Status;
  }

  //
  // Step 2: Update Pad area
  //
  InitializeListHead (&NewFileList);

  if (IS_FFS_FILE2 (OldPadFileHeader)) {
    PadAreaLength = FFS_FILE2_SIZE (OldPadFileHeader) - sizeof (EFI_FFS_FILE_HEADER);
    PadFileHeader = (EFI_FFS_FILE_HEADER *) ((UINT8 *) OldPadFileHeader + sizeof (EFI_FFS_FILE_HEADER2));
  } else {
    PadAreaLength = FFS_FILE_SIZE (OldPadFileHeader) - sizeof (EFI_FFS_FILE_HEADER);
    PadFileHeader = (EFI_FFS_FILE_HEADER *) ((UINT8 *) OldPadFileHeader + sizeof (EFI_FFS_FILE_HEADER));
  }

  if (PadSize != 0) {
    //
    // Insert a PAD file before to achieve required alignment
    //
    FvFillPadFile (PadFileHeader, PadSize);
    NewFileListEntry            = AllocatePool (sizeof (FFS_FILE_LIST_ENTRY));
    ASSERT (NewFileListEntry   != NULL);
    NewFileListEntry->FfsHeader = (UINT8 *) PadFileHeader;
    InsertTailList (&NewFileList, &NewFileListEntry->Link);
  }

  FileHeader = (EFI_FFS_FILE_HEADER *) ((UINT8 *) PadFileHeader + PadSize);

  Status = FvFillFfsFile (
            FileHeader,
            FfsFileBuffer,
            BufferSize,
            ActualFileSize,
            FileName,
            FileType,
            FileAttributes
            );
  if (EFI_ERROR (Status)) {
    FreeFileList (&NewFileList);
    return Status;
  }

  NewFileListEntry            = AllocatePool (sizeof (FFS_FILE_LIST_ENTRY));
  ASSERT (NewFileListEntry   != NULL);

  NewFileListEntry->FfsHeader = (UINT8 *) FileHeader;
  InsertTailList (&NewFileList, &NewFileListEntry->Link);

  FvDevice->CurrentFfsFile = NewFileListEntry;

  if (PadAreaLength > (BufferSize + PadSize)) {
    if ((PadAreaLength - BufferSize - PadSize) >= sizeof (EFI_FFS_FILE_HEADER)) {
      //
      // we can insert another PAD file
      //
      TailPadFileHeader = (EFI_FFS_FILE_HEADER *) ((UINT8 *) FileHeader + BufferSize);
      FvFillPadFile (TailPadFileHeader, PadAreaLength - BufferSize - PadSize);

      NewFileListEntry            = AllocatePool (sizeof (FFS_FILE_LIST_ENTRY));
      ASSERT (NewFileListEntry   != NULL);

      NewFileListEntry->FfsHeader = (UINT8 *) TailPadFileHeader;
      InsertTailList (&NewFileList, &NewFileListEntry->Link);
    } else {
      //
      // because left size cannot hold another PAD file header,
      // adjust the writing file size (just in cache)
      //
      FvAdjustFfsFile (
        FvDevice->ErasePolarity,
        FileHeader,
        PadAreaLength - BufferSize - PadSize
        );
    }
  }
  //
  // Start writing to FV
  //
  if (IS_FFS_FILE2 (OldPadFileHeader)) {
    StartPos = (UINT8 *) OldPadFileHeader + sizeof (EFI_FFS_FILE_HEADER2);
  } else {
    StartPos = (UINT8 *) OldPadFileHeader + sizeof (EFI_FFS_FILE_HEADER);
  }

  Offset          = (UINTN) (StartPos - FvDevice->CachedFv);

  NumBytesWritten = PadAreaLength;
  Status = FvcWrite (
            FvDevice,
            Offset,
            &NumBytesWritten,
            StartPos
            );
  if (EFI_ERROR (Status)) {
    FreeFileList (&NewFileList);
    FvDevice->CurrentFfsFile = NULL;
    return Status;
  }

  //
  // Step 3: Mark Pad file header as EFI_FILE_HEADER_INVALID
  //
  SetFileState (EFI_FILE_HEADER_INVALID, OldPadFileHeader);

  StartPos = PadFileEntry->FfsHeader;

  Offset          = (UINTN) (StartPos - FvDevice->CachedFv);
  StateOffset     = Offset + (UINT8 *) &OldPadFileHeader->State - (UINT8 *) OldPadFileHeader;

  NumBytesWritten = sizeof (EFI_FFS_FILE_STATE);
  Status = FvcWrite (
            FvDevice,
            StateOffset,
            &NumBytesWritten,
            &OldPadFileHeader->State
            );
  if (EFI_ERROR (Status)) {
    SetFileState (EFI_FILE_HEADER_INVALID, OldPadFileHeader);
    FreeFileList (&NewFileList);
    FvDevice->CurrentFfsFile = NULL;
    return Status;
  }

  //
  // If all successfully, update FFS_FILE_LIST
  //

  //
  // Delete old pad file entry
  //
  FfsEntry      = (FFS_FILE_LIST_ENTRY *) PadFileEntry->Link.BackLink;
  NextFfsEntry  = (FFS_FILE_LIST_ENTRY *) PadFileEntry->Link.ForwardLink;

  FreePool (PadFileEntry);

  FfsEntry->Link.ForwardLink          = NewFileList.ForwardLink;
  (NewFileList.ForwardLink)->BackLink = &FfsEntry->Link;
  NextFfsEntry->Link.BackLink         = NewFileList.BackLink;
  (NewFileList.BackLink)->ForwardLink = &NextFfsEntry->Link;

  return EFI_SUCCESS;
}

/**
  Free all FfsBuffer.

  @param NumOfFiles      Number of FfsBuffer.
  @param FfsBuffer       An array of pointer to an FFS File Buffer

**/
VOID
FreeFfsBuffer (
  IN UINTN    NumOfFiles,
  IN UINT8    **FfsBuffer
  )
{
  UINTN Index;
  for (Index = 0; Index < NumOfFiles; Index++) {
    if (FfsBuffer[Index] != NULL) {
      FreePool (FfsBuffer[Index]);
    }
  }
}

/**
  Create multiple files within a PAD File area.

  @param FvDevice        Firmware Volume Device.
  @param PadFileEntry    The pad file entry to be written in.
  @param NumOfFiles      Total File number to be written.
  @param BufferSize      The array of buffer size of each FfsBuffer.
  @param ActualFileSize  The array of actual file size.
  @param PadSize         The array of leading pad file size for each FFS File
  @param FfsBuffer       The array of Ffs Buffer pointer.
  @param FileData        The array of EFI_FV_WRITE_FILE_DATA structure,
                         used to get name, attributes, type, etc.

  @retval EFI_SUCCESS           Add the input multiple files into PAD file area.
  @retval EFI_OUT_OF_RESOURCES  No enough memory is allocated.
  @retval other error           Files can't be added into PAD file area.

**/
EFI_STATUS
FvCreateMultipleFilesInsidePadFile (
  IN FV_DEVICE              *FvDevice,
  IN FFS_FILE_LIST_ENTRY    *PadFileEntry,
  IN UINTN                  NumOfFiles,
  IN UINTN                  *BufferSize,
  IN UINTN                  *ActualFileSize,
  IN UINTN                  *PadSize,
  IN UINT8                  **FfsBuffer,
  IN EFI_FV_WRITE_FILE_DATA *FileData
  )
{
  EFI_STATUS                          Status;
  EFI_FFS_FILE_HEADER                 *OldPadFileHeader;
  UINTN                               Index;
  EFI_FFS_FILE_HEADER                 *PadFileHeader;
  EFI_FFS_FILE_HEADER                 *FileHeader;
  EFI_FFS_FILE_HEADER                 *TailPadFileHeader;
  UINTN                               TotalSize;
  UINTN                               PadAreaLength;
  LIST_ENTRY                          NewFileList;
  FFS_FILE_LIST_ENTRY                 *NewFileListEntry;
  UINTN                               Offset;
  UINTN                               NumBytesWritten;
  UINT8                               *StartPos;
  FFS_FILE_LIST_ENTRY                 *FfsEntry;
  FFS_FILE_LIST_ENTRY                 *NextFfsEntry;

  InitializeListHead (&NewFileList);

  NewFileListEntry  = NULL;

  OldPadFileHeader  = (EFI_FFS_FILE_HEADER *) PadFileEntry->FfsHeader;
  if (IS_FFS_FILE2 (OldPadFileHeader)) {
    PadAreaLength = FFS_FILE2_SIZE (OldPadFileHeader) - sizeof (EFI_FFS_FILE_HEADER2);
  } else {
    PadAreaLength = FFS_FILE_SIZE (OldPadFileHeader) - sizeof (EFI_FFS_FILE_HEADER);
  }

  Status = UpdateHeaderBit (
            FvDevice,
            OldPadFileHeader,
            EFI_FILE_MARKED_FOR_UPDATE
            );
  if (EFI_ERROR (Status)) {
    return Status;
  }
  //
  // Update PAD area
  //
  TotalSize     = 0;
  if (IS_FFS_FILE2 (OldPadFileHeader)) {
    PadFileHeader = (EFI_FFS_FILE_HEADER *) ((UINT8 *) OldPadFileHeader + sizeof (EFI_FFS_FILE_HEADER2));
  } else {
    PadFileHeader = (EFI_FFS_FILE_HEADER *) ((UINT8 *) OldPadFileHeader + sizeof (EFI_FFS_FILE_HEADER));
  }
  FileHeader    = PadFileHeader;

  for (Index = 0; Index < NumOfFiles; Index++) {
    if (PadSize[Index] != 0) {
      FvFillPadFile (PadFileHeader, PadSize[Index]);
      NewFileListEntry = AllocatePool (sizeof (FFS_FILE_LIST_ENTRY));
      if (NewFileListEntry == NULL) {
        FreeFileList (&NewFileList);
        return EFI_OUT_OF_RESOURCES;
      }

      NewFileListEntry->FfsHeader = (UINT8 *) PadFileHeader;
      InsertTailList (&NewFileList, &NewFileListEntry->Link);
    }

    FileHeader = (EFI_FFS_FILE_HEADER *) ((UINT8 *) PadFileHeader + PadSize[Index]);
    Status = FvFillFfsFile (
              FileHeader,
              FfsBuffer[Index],
              BufferSize[Index],
              ActualFileSize[Index],
              FileData[Index].NameGuid,
              FileData[Index].Type,
              FileData[Index].FileAttributes
              );
    if (EFI_ERROR (Status)) {
      FreeFileList (&NewFileList);
      return Status;
    }

    NewFileListEntry = AllocatePool (sizeof (FFS_FILE_LIST_ENTRY));
    if (NewFileListEntry == NULL) {
      FreeFileList (&NewFileList);
      return EFI_OUT_OF_RESOURCES;
    }

    NewFileListEntry->FfsHeader = (UINT8 *) FileHeader;
    InsertTailList (&NewFileList, &NewFileListEntry->Link);

    PadFileHeader = (EFI_FFS_FILE_HEADER *) ((UINT8 *) FileHeader + BufferSize[Index]);
    TotalSize += PadSize[Index];
    TotalSize += BufferSize[Index];
  }

  FvDevice->CurrentFfsFile = NewFileListEntry;
  //
  // Maybe we need a tail pad file
  //
  if (PadAreaLength > TotalSize) {
    if ((PadAreaLength - TotalSize) >= sizeof (EFI_FFS_FILE_HEADER)) {
      //
      // we can insert another PAD file
      //
      TailPadFileHeader = (EFI_FFS_FILE_HEADER *) ((UINT8 *) FileHeader + BufferSize[NumOfFiles - 1]);
      FvFillPadFile (TailPadFileHeader, PadAreaLength - TotalSize);

      NewFileListEntry = AllocatePool (sizeof (FFS_FILE_LIST_ENTRY));
      if (NewFileListEntry == NULL) {
        FreeFileList (&NewFileList);
        FvDevice->CurrentFfsFile = NULL;
        return EFI_OUT_OF_RESOURCES;
      }

      NewFileListEntry->FfsHeader = (UINT8 *) TailPadFileHeader;
      InsertTailList (&NewFileList, &NewFileListEntry->Link);
    } else {
      //
      // because left size cannot hold another PAD file header,
      // adjust the writing file size (just in cache)
      //
      FvAdjustFfsFile (
        FvDevice->ErasePolarity,
        FileHeader,
        PadAreaLength - TotalSize
        );
    }
  }
  //
  // Start writing to FV
  //
  if (IS_FFS_FILE2 (OldPadFileHeader)) {
    StartPos = (UINT8 *) OldPadFileHeader + sizeof (EFI_FFS_FILE_HEADER2);
  } else {
    StartPos = (UINT8 *) OldPadFileHeader + sizeof (EFI_FFS_FILE_HEADER);
  }

  Offset          = (UINTN) (StartPos - FvDevice->CachedFv);

  NumBytesWritten = PadAreaLength;
  Status = FvcWrite (
            FvDevice,
            Offset,
            &NumBytesWritten,
            StartPos
            );
  if (EFI_ERROR (Status)) {
    FreeFileList (&NewFileList);
    FvDevice->CurrentFfsFile = NULL;
    return Status;
  }

  Status = UpdateHeaderBit (
            FvDevice,
            OldPadFileHeader,
            EFI_FILE_HEADER_INVALID
            );
  if (EFI_ERROR (Status)) {
    FreeFileList (&NewFileList);
    FvDevice->CurrentFfsFile = NULL;
    return Status;
  }

  //
  // Update File List Link
  //

  //
  // First delete old pad file entry
  //
  FfsEntry      = (FFS_FILE_LIST_ENTRY *) PadFileEntry->Link.BackLink;
  NextFfsEntry  = (FFS_FILE_LIST_ENTRY *) PadFileEntry->Link.ForwardLink;

  FreePool (PadFileEntry);

  FfsEntry->Link.ForwardLink          = NewFileList.ForwardLink;
  (NewFileList.ForwardLink)->BackLink = &FfsEntry->Link;
  NextFfsEntry->Link.BackLink         = NewFileList.BackLink;
  (NewFileList.BackLink)->ForwardLink = &NextFfsEntry->Link;

  return EFI_SUCCESS;
}

/**
  Create multiple files within the Free Space.

  @param FvDevice        Firmware Volume Device.
  @param FreeSpaceEntry  Indicating in which Free Space(Cache) the multiple files will be inserted.
  @param NumOfFiles      Total File number to be written.
  @param BufferSize      The array of buffer size of each FfsBuffer.
  @param ActualFileSize  The array of actual file size.
  @param PadSize         The array of leading pad file size for each FFS File
  @param FfsBuffer       The array of Ffs Buffer pointer.
  @param FileData        The array of EFI_FV_WRITE_FILE_DATA structure,
                         used to get name, attributes, type, etc.

  @retval EFI_SUCCESS           Add the input multiple files into PAD file area.
  @retval EFI_OUT_OF_RESOURCES  No enough memory is allocated.
  @retval other error           Files can't be added into PAD file area.

**/
EFI_STATUS
FvCreateMultipleFilesInsideFreeSpace (
  IN FV_DEVICE              *FvDevice,
  IN FREE_SPACE_ENTRY       *FreeSpaceEntry,
  IN UINTN                  NumOfFiles,
  IN UINTN                  *BufferSize,
  IN UINTN                  *ActualFileSize,
  IN UINTN                  *PadSize,
  IN UINT8                  **FfsBuffer,
  IN EFI_FV_WRITE_FILE_DATA *FileData
  )
{
  EFI_STATUS                          Status;
  UINTN                               Index;
  EFI_FFS_FILE_HEADER                 *PadFileHeader;
  EFI_FFS_FILE_HEADER                 *FileHeader;
  UINTN                               TotalSize;
  LIST_ENTRY                          NewFileList;
  FFS_FILE_LIST_ENTRY                 *NewFileListEntry;
  UINTN                               Offset;
  UINTN                               NumBytesWritten;
  UINT8                               *StartPos;

  InitializeListHead (&NewFileList);

  NewFileListEntry  = NULL;

  TotalSize     = 0;
  StartPos      = FreeSpaceEntry->StartingAddress;
  PadFileHeader = (EFI_FFS_FILE_HEADER *) StartPos;
  FileHeader    = PadFileHeader;

  for (Index = 0; Index < NumOfFiles; Index++) {
    if (PadSize[Index] != 0) {
      FvFillPadFile (PadFileHeader, PadSize[Index]);
      NewFileListEntry = AllocatePool (sizeof (FFS_FILE_LIST_ENTRY));
      if (NewFileListEntry == NULL) {
        FreeFileList (&NewFileList);
        return EFI_OUT_OF_RESOURCES;
      }

      NewFileListEntry->FfsHeader = (UINT8 *) PadFileHeader;
      InsertTailList (&NewFileList, &NewFileListEntry->Link);
    }

    FileHeader = (EFI_FFS_FILE_HEADER *) ((UINT8 *) PadFileHeader + PadSize[Index]);
    Status = FvFillFfsFile (
              FileHeader,
              FfsBuffer[Index],
              BufferSize[Index],
              ActualFileSize[Index],
              FileData[Index].NameGuid,
              FileData[Index].Type,
              FileData[Index].FileAttributes
              );
    if (EFI_ERROR (Status)) {
      FreeFileList (&NewFileList);
      return Status;
    }

    NewFileListEntry = AllocatePool (sizeof (FFS_FILE_LIST_ENTRY));
    if (NewFileListEntry == NULL) {
      FreeFileList (&NewFileList);
      return EFI_OUT_OF_RESOURCES;
    }

    NewFileListEntry->FfsHeader = (UINT8 *) FileHeader;
    InsertTailList (&NewFileList, &NewFileListEntry->Link);

    PadFileHeader = (EFI_FFS_FILE_HEADER *) ((UINT8 *) FileHeader + BufferSize[Index]);
    TotalSize += PadSize[Index];
    TotalSize += BufferSize[Index];
  }

  if (FreeSpaceEntry->Length < TotalSize) {
    FreeFileList (&NewFileList);
    return EFI_OUT_OF_RESOURCES;
  }

  FvDevice->CurrentFfsFile = NewFileListEntry;

  //
  // Start writing to FV
  //
  Offset          = (UINTN) (StartPos - FvDevice->CachedFv);

  NumBytesWritten = TotalSize;
  Status = FvcWrite (
            FvDevice,
            Offset,
            &NumBytesWritten,
            StartPos
            );
  if (EFI_ERROR (Status)) {
    FreeFileList (&NewFileList);
    FvDevice->CurrentFfsFile = NULL;
    return Status;
  }

  FreeSpaceEntry->Length -= TotalSize;
  FreeSpaceEntry->StartingAddress += TotalSize;

  NewFileListEntry = (FFS_FILE_LIST_ENTRY *) (NewFileList.ForwardLink);

  while (NewFileListEntry != (FFS_FILE_LIST_ENTRY *) &NewFileList) {
    InsertTailList (&FvDevice->FfsFileListHeader, &NewFileListEntry->Link);
    NewFileListEntry = (FFS_FILE_LIST_ENTRY *) (NewFileListEntry->Link.ForwardLink);
  }

  return EFI_SUCCESS;
}

/**
  Write multiple files into FV in reliable method.

  @param FvDevice        Firmware Volume Device.
  @param NumOfFiles      Total File number to be written.
  @param FileData        The array of EFI_FV_WRITE_FILE_DATA structure,
                         used to get name, attributes, type, etc
  @param FileOperation   The array of operation for each file.

  @retval EFI_SUCCESS            Files are added into FV.
  @retval EFI_OUT_OF_RESOURCES   No enough free PAD files to add the input files.
  @retval EFI_INVALID_PARAMETER  File number is less than or equal to 1.
  @retval EFI_UNSUPPORTED        File number exceeds the supported max numbers of files.

**/
EFI_STATUS
FvCreateMultipleFiles (
  IN  FV_DEVICE               *FvDevice,
  IN  UINTN                   NumOfFiles,
  IN  EFI_FV_WRITE_FILE_DATA  *FileData,
  IN  BOOLEAN                 *FileOperation
  )
{
  EFI_STATUS                    Status;
  UINT8                         *FfsBuffer[MAX_FILES];
  UINTN                         Index1;
  UINTN                         Index2;
  UINTN                         BufferSize[MAX_FILES];
  UINTN                         ActualFileSize[MAX_FILES];
  UINTN                         RequiredAlignment[MAX_FILES];
  UINTN                         PadSize[MAX_FILES];
  FFS_FILE_LIST_ENTRY           *PadFileEntry;
  UINTN                         TotalSizeNeeded;
  FREE_SPACE_ENTRY              *FreeSpaceEntry;
  EFI_FIRMWARE_VOLUME2_PROTOCOL *Fv;
  UINTN                         Key;
  EFI_GUID                      FileNameGuid;
  EFI_FV_FILETYPE               OldFileType;
  EFI_FV_FILE_ATTRIBUTES        OldFileAttributes;
  UINTN                         OldFileSize;
  FFS_FILE_LIST_ENTRY           *OldFfsFileEntry[MAX_FILES];
  EFI_FFS_FILE_HEADER           *OldFileHeader[MAX_FILES];
  BOOLEAN                       IsCreateFile;
  UINTN                         HeaderSize;

  //
  // To use this function, we must ensure that the NumOfFiles is great
  // than 1
  //
  if (NumOfFiles <= 1) {
    return EFI_INVALID_PARAMETER;
  }

  if (NumOfFiles > MAX_FILES) {
    return EFI_UNSUPPORTED;
  }

  Fv = &FvDevice->Fv;

  SetMem (FfsBuffer, NumOfFiles, 0);
  SetMem (RequiredAlignment, NumOfFiles, 8);
  SetMem (PadSize, NumOfFiles, 0);
  ZeroMem (OldFfsFileEntry, sizeof (OldFfsFileEntry));
  ZeroMem (OldFileHeader, sizeof (OldFileHeader));

  //
  // Adjust file size
  //
  for (Index1 = 0; Index1 < NumOfFiles; Index1++) {
    HeaderSize = sizeof (EFI_FFS_FILE_HEADER);
    ActualFileSize[Index1] = FileData[Index1].BufferSize + HeaderSize;
    if (ActualFileSize[Index1] > 0x00FFFFFF) {
      HeaderSize = sizeof (EFI_FFS_FILE_HEADER2);
      ActualFileSize[Index1] = FileData[Index1].BufferSize + HeaderSize;
    }
    BufferSize[Index1]     = ActualFileSize[Index1];

    if (BufferSize[Index1] == HeaderSize) {
      //
      // clear file attributes, zero-length file does not have any attributes
      //
      FileData[Index1].FileAttributes = 0;
    }

    while ((BufferSize[Index1] & 0x07) != 0) {
      BufferSize[Index1]++;
    }

    FfsBuffer[Index1] = AllocateZeroPool (BufferSize[Index1]);

    //
    // Copy File Data into FileBuffer
    //
    CopyMem (
      FfsBuffer[Index1] + HeaderSize,
      FileData[Index1].Buffer,
      FileData[Index1].BufferSize
      );

    if (FvDevice->ErasePolarity == 1) {
      for (Index2 = 0; Index2 < HeaderSize; Index2++) {
        FfsBuffer[Index1][Index2] = (UINT8)~FfsBuffer[Index1][Index2];
      }
    }

    if ((FileData[Index1].FileAttributes & EFI_FV_FILE_ATTRIB_ALIGNMENT) != 0) {
      RequiredAlignment[Index1] = GetRequiredAlignment (FileData[Index1].FileAttributes);
    }
    //
    // If update file, mark the original file header to
    // EFI_FILE_MARKED_FOR_UPDATE
    //
    IsCreateFile = FileOperation[Index1];
    if (!IsCreateFile) {

      Key = 0;
      do {
        OldFileType = 0;
        Status = Fv->GetNextFile (
                      Fv,
                      &Key,
                      &OldFileType,
                      &FileNameGuid,
                      &OldFileAttributes,
                      &OldFileSize
                      );
        if (EFI_ERROR (Status)) {
          FreeFfsBuffer (NumOfFiles, FfsBuffer);
          return Status;
        }
      } while (!CompareGuid (&FileNameGuid, FileData[Index1].NameGuid));

      //
      // Get FfsFileEntry from the search key
      //
      OldFfsFileEntry[Index1]  = (FFS_FILE_LIST_ENTRY *) Key;
      OldFileHeader[Index1]    = (EFI_FFS_FILE_HEADER *) OldFfsFileEntry[Index1]->FfsHeader;
      Status = UpdateHeaderBit (
                FvDevice,
                OldFileHeader[Index1],
                EFI_FILE_MARKED_FOR_UPDATE
                );
      if (EFI_ERROR (Status)) {
        FreeFfsBuffer (NumOfFiles, FfsBuffer);
        return Status;
      }
    }
  }
  //
  // First to search a suitable pad file that can hold so
  // many files
  //
  Status = FvSearchSuitablePadFile (
            FvDevice,
            NumOfFiles,
            BufferSize,
            RequiredAlignment,
            PadSize,
            &TotalSizeNeeded,
            &PadFileEntry
            );

  if (Status == EFI_NOT_FOUND) {
    //
    // Try to find a free space that can hold these files
    //
    Status = FvSearchSuitableFreeSpace (
              FvDevice,
              NumOfFiles,
              BufferSize,
              RequiredAlignment,
              PadSize,
              &TotalSizeNeeded,
              &FreeSpaceEntry
              );
    if (EFI_ERROR (Status)) {
      FreeFfsBuffer (NumOfFiles, FfsBuffer);
      return EFI_OUT_OF_RESOURCES;
    }
    Status = FvCreateMultipleFilesInsideFreeSpace (
              FvDevice,
              FreeSpaceEntry,
              NumOfFiles,
              BufferSize,
              ActualFileSize,
              PadSize,
              FfsBuffer,
              FileData
              );

  } else {
    //
    // Create multiple files inside such a pad file
    // to achieve lock-step update
    //
    Status = FvCreateMultipleFilesInsidePadFile (
              FvDevice,
              PadFileEntry,
              NumOfFiles,
              BufferSize,
              ActualFileSize,
              PadSize,
              FfsBuffer,
              FileData
              );
  }

  FreeFfsBuffer (NumOfFiles, FfsBuffer);

  if (EFI_ERROR (Status)) {
    return Status;
  }
  //
  // Delete those updated files
  //
  for (Index1 = 0; Index1 < NumOfFiles; Index1++) {
    IsCreateFile = FileOperation[Index1];
    if (!IsCreateFile && OldFfsFileEntry[Index1] != NULL) {
      (OldFfsFileEntry[Index1]->Link.BackLink)->ForwardLink  = OldFfsFileEntry[Index1]->Link.ForwardLink;
      (OldFfsFileEntry[Index1]->Link.ForwardLink)->BackLink  = OldFfsFileEntry[Index1]->Link.BackLink;
      FreePool (OldFfsFileEntry[Index1]);
    }
  }
  //
  // Set those files' state to EFI_FILE_DELETED
  //
  for (Index1 = 0; Index1 < NumOfFiles; Index1++) {
    IsCreateFile = FileOperation[Index1];
    if (!IsCreateFile && OldFileHeader[Index1] != NULL) {
      Status = UpdateHeaderBit (FvDevice, OldFileHeader[Index1], EFI_FILE_DELETED);
      if (EFI_ERROR (Status)) {
        return Status;
      }
    }
  }

  return EFI_SUCCESS;
}
